/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package core.connector.email.http.skydrive;

import gui.factory.components.AbstractDownload;

import java.io.File;
import java.io.IOException;
import java.io.StringReader;
import java.net.ProtocolException;
import java.net.UnknownHostException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.script.Bindings;
import javax.script.ScriptContext;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.xml.namespace.QName;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathFunction;
import javax.xml.xpath.XPathFunctionException;
import javax.xml.xpath.XPathFunctionResolver;

import org.apache.commons.httpclient.Header;
import org.apache.commons.httpclient.NameValuePair;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.methods.PostMethod;
import org.htmlcleaner.PrettyXmlSerializer;
import org.htmlcleaner.TagNode;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.xml.sax.InputSource;

import core.connector.ConnectionDetails;
import core.connector.ConnectorFactory;
import core.connector.ConnectorType;
import core.connector.DownloadableItem;
import core.connector.email.http.HttpConnectorBase;
import core.util.JGoogleAnalytics;
import core.xml.XPathResultNavigation;

/**
 *
 * @author Vinicius
 */
public class HttpConnectorSkyDrive extends HttpConnectorBase{

	private static final Map<String,XPathFunction> xpathFunctions = new HashMap<String, XPathFunction>();
	
	static{
		xpathFunctions.put("lower-case", new XPathFunction() {			
			@Override
			@SuppressWarnings("rawtypes")
			public Object evaluate(List args) throws XPathFunctionException {
				String in = (String)args.get(0);
				return in.toLowerCase();
			}
		});
	}
	
    private XPathExpression     homeScriptPath;
    private XPathExpression     sftValuePath;
    private XPathExpression     metaRefreshPath;
    private XPathExpression     storageInputsFormPath;
    private XPathExpression     storageFormactionPath;
    private XPathExpression     driveLinkPath;
    private Pattern             metaUrlRefreshPattern;
    private String              homeDriveUrl;
    private String              driveTitle;

    public HttpConnectorSkyDrive(ConnectionDetails connectionDetails){
        super(connectionDetails);
        JGoogleAnalytics.trackAsynchronously("Connector HTTP - SkyDrive");
    }

    {
        if(!user.contains("@") || !user.contains("/")){
            throw new IllegalStateException("Cannot connect: user must have the following format : login@domain/drive");
        }else{
            int dindex = user.indexOf("/");
            driveTitle = user.substring(dindex+1);
            user = user.substring(0,dindex);
        }
        xpath.setXPathFunctionResolver(new XPathFunctionResolver() {			
			@Override
			public XPathFunction resolveFunction(QName functionName, int arity) {
				if(functionName.getLocalPart().equals("lower-case")){
					return xpathFunctions.get("lower-case");
				}
				return null;
			}
		});        
        try {
            homeScriptPath = xpath.compile("//script[@type='text/javascript' and not(@src)]");
            sftValuePath = xpath.compile("/input/@value");
            metaRefreshPath = xpath.compile("//meta[p2m:lower-case(@http-equiv)='refresh']/@content");
            storageInputsFormPath = xpath.compile("//form[@name='fmHF']/input[@type='hidden']");
            storageFormactionPath = xpath.compile("//form[@name='fmHF']/@action");
            driveLinkPath = xpath.compile("//a[@title='"+driveTitle+"']/@href");
            metaUrlRefreshPattern = Pattern.compile("(URL|url)=(\"|'){0,1}.*(\"|'){0,1}");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public boolean connect() {
        System.out.println("CONECTANDO...");
        if (user.equals("") || passwd.equals(""))
        {
            throw new IllegalStateException("Cannot connect: user and password not specified.");
        }
        StepResult step = stepGet("http://skydrive.live.com/");
        String resultado = step.getResultado();
        HomeLoginParams params = getLoginParams(resultado);
        String sft = null;
        try {
            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
            DocumentBuilder builder = factory.newDocumentBuilder();
            Document root = builder.parse(new InputSource(new StringReader(params.getSFT())));
            XPathResultNavigation result = new XPathResultNavigation(sftValuePath, root);
            sft = result.getAttributeValue();
        } catch (Exception e) {
            e.printStackTrace();
        }
        if(sft != null){
            PostMethod post = new PostMethod(params.getPostUrl());
            post.addRequestHeader("Accept", "text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png;q=0.5");
            post.addRequestHeader("Connection", "keep-alive");
            post.addRequestHeader("Accept-Encoding", "gzip");
            post.addRequestHeader("Referer", step.getLocation());
            post.addRequestHeader("Content-Type", "application/x-www-form-urlencoded");
            post.addParameter("login", user);
            post.addParameter("passwd", passwd);
            post.addParameter("type", "11");
            post.addParameter("LoginOptions", "3");
            post.addParameter("MEST", "");
            post.addParameter("PPSX", "Passpor");
            post.addParameter("PPFT", sft);
            post.addParameter("idsbho", "1");
            post.addParameter("PwdPad", "");
            post.addParameter("sso", "");
            post.addParameter("i1", "");
            post.addParameter("i2", "1");
            post.addParameter("i3", genI3());
            post.addParameter("i4", "");
            post.addParameter("i12", "1");
            setCookies(post);

            try {
                post.setFollowRedirects(false);
                client.executeMethod(post);
                resultado = getResponseBodyAsString(post);
                Header[] cookies = post.getResponseHeaders("set-cookie");
                for(Header cookie : cookies){
                    buildCookie(cookie);
                }
            } catch (Exception e) {
                e.printStackTrace();
                return false;
            }
            String url = null;
            try {
                TagNode root = getCleaner().clean(resultado);
                Document dom = getDomSerializer().createDOM(root);
                XPathResultNavigation result = new XPathResultNavigation(metaRefreshPath, dom);
                url = result.getAttributeValue();
            } catch (Exception e) {
                e.printStackTrace();
            }
            if(url != null){
                step = stepGet(prepareRefreshUrl(url));
                StorageParams storageForm = getStorageForm(step.getResultado());
                if(storageForm.getAction() != null && storageForm.getInputs() != null){
                    post = new PostMethod(storageForm.getAction());
                    post.addRequestHeader("Accept", "text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png;q=0.5");
                    post.addRequestHeader("Connection", "keep-alive");
                    post.addRequestHeader("Accept-Encoding", "gzip");
                    post.addRequestHeader("Referer", step.getLocation());
                    post.addRequestHeader("Content-Type", "application/x-www-form-urlencoded");
                    post.addParameters(storageForm.getInputs());
                    setCookies(post);

                    String home = null;
                    try {
                        post.setFollowRedirects(false);
                        client.executeMethod(post);
                        resultado = getResponseBodyAsString(post);
                        Header[] cookies = post.getResponseHeaders("set-cookie");
                        for(Header cookie : cookies){
                            buildCookie(cookie);
                        }
                        Header location = post.getResponseHeader("location");
                        if(location != null){
                            home = location.getValue();
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                        return false;
                    }
                    if(home != null){
                        step = stepGet(home);
                        resultado = step.getResultado();
                        try {
                            TagNode root = getCleaner().clean(resultado);
                            Document dom = getDomSerializer().createDOM(root);
                            new PrettyXmlSerializer(getCleaner().getProperties()).writeToFile(root, "c:/sky.xml");
                            XPathResultNavigation result = new XPathResultNavigation(driveLinkPath, dom);
                            homeDriveUrl = result.getAttributeValue();
                            if(homeDriveUrl != null){
                                this.status = STATUS_CONNECTED;
                                return true;
                            }
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }
        return false;
    }

    private StorageParams getStorageForm(String resultado){
        NameValuePair[] storageParams = null;
        String action = null;
        try {
            TagNode root = getCleaner().clean(resultado);
            Document dom = getDomSerializer().createDOM(root);
            XPathResultNavigation inputsResult = new XPathResultNavigation(storageInputsFormPath, dom);
            XPathResultNavigation actionResult = new XPathResultNavigation(storageFormactionPath, dom);
            action = actionResult.getAttributeValue();
            List<Element> els = inputsResult.getElements();
            if(els != null){
                storageParams = new NameValuePair[els.size()];
                for(int i = 0;i < els.size();i++){
                    String name = els.get(i).getAttribute("name");
                    String value = els.get(i).getAttribute("value");
                    storageParams[i] = new NameValuePair(name,value);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return new StorageParams(action, storageParams);
    }

    private StepResult stepGet(String url){
        String lasturl = null;
        String resultado = null;
        while(url != null){
            GetMethod get = new GetMethod(url);
            get.addRequestHeader("Accept","text/html");
            get.addRequestHeader("Accept-Language","pt-br,en-us");
            get.addRequestHeader("Accept-Charset","ISO-8859-1,utf-8");
            get.addRequestHeader("Accept-Encoding","gzip");
            get.addRequestHeader("Connection","keep-alive");

            get.setFollowRedirects(false);
            setCookies(get);
            int status = -1;
            try{
                status = client.executeMethod(get);
                Header[] cookies = get.getResponseHeaders("set-cookie");
                for(Header cookie : cookies){
                    buildCookie(cookie);
                }
                Header location = get.getResponseHeader("location");
                if(location != null){
                    url = location.getValue();
                    lasturl = url;
                }else{
                    resultado = getResponseBodyAsString(get);
                    url = null;
                }
            }catch(Exception ex){
                ex.printStackTrace();
            }finally{
                get.releaseConnection();
            }
        }
        return new StepResult(lasturl, resultado);
    }

    private String prepareRefreshUrl(String url){
        Matcher m = metaUrlRefreshPattern.matcher(url);
        if(m.find()){
            url = m.group();
            url = url.substring(url.indexOf("=")+1);
            url = url.replaceAll("(\"|')", "");
        }else{
            url = null;
        }
        return url;
    }

    private String genI3(){
        double s = Math.random() * 100.0 / 10.0;
        int i3 = (int)((s < 5 ? s + 5 : s) * 1000.0);
        return String.valueOf(i3);
    }

    private HomeLoginParams getLoginParams(String page){
        getCleaner().getProperties().setUseCdataForScriptAndStyle(false);
        TagNode root = getCleaner().clean(page);
        try {
            Document dom = getDomSerializer().createDOM(root);
            XPathResultNavigation result = new XPathResultNavigation(homeScriptPath, dom);
            return getLoginParams(result.getElementsValues());
        } catch (Exception e) {
            e.printStackTrace();
        }finally{
            getCleaner().getProperties().setUseCdataForScriptAndStyle(true);
        }
        return null;
    }

    private HomeLoginParams getLoginParams(List<String> scripts){
        Iterator<String> iterator = scripts.iterator();
        HomeLoginParams params = null;
        boolean finded = false;
        while(iterator.hasNext() && !finded){
            String script = iterator.next();
            if(script.indexOf("srf_uPost") > -1){
                finded = (params = parseLoginScript(script)) != null;
            }
        }
        return params;
    }

    private HomeLoginParams parseLoginScript(String script){
        ScriptEngineManager mgr = new ScriptEngineManager();
        ScriptEngine engine = mgr.getEngineByName("JavaScript");
        try {
            engine.eval(script);
            Bindings bind = engine.getBindings(ScriptContext.ENGINE_SCOPE);
            Map<String,String> params = new HashMap<String, String>();
            for(String key : bind.keySet()){
                params.put(key, bind.get(key).toString());
            }
            return new HomeLoginParams(params);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    @Override
    public int getNumberOfFiles() {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    public boolean getNextFile() throws Exception {
        throw new UnsupportedOperationException("Not supported yet.");
    }
    
    @Override
    public void getFile(DownloadableItem downloadableItem, File pastaDestino, AbstractDownload downloadGUI) throws IOException, ProtocolException, UnknownHostException, Exception {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    public void disconnect() {
        throw new UnsupportedOperationException("Not supported yet.");
    }


    public static void main(String[] args) throws Exception{
        HttpConnectorSkyDrive sky = new HttpConnectorSkyDrive(
                new ConnectionDetails(ConnectorFactory.HTTP_CONNECTOR_SKY_DRIVE, "renatoyann1@hotmail.com/TESTMR", "pympos", ConnectorType.HTTP)
        );
        sky.connect();
    }

}
